home *** CD-ROM | disk | FTP | other *** search
/ Libris Britannia 4 / science library(b).zip / science library(b) / INFO / UNDOCDOS.ZIP / ERRATA
Internet Message Format  |  1991-04-02  |  48KB

  1. Date: Mon, 18 Mar 91 15:03:13 EST
  2. From: Andrew Schulman <andrew@pharlap.com>
  3. Subject: UNDOCUDOS errata -- part 1 of 3
  4.  
  5.              Errata, Commentary, and "Release Notes" for
  6.                           UNDOCUMENTED DOS:
  7.  A Programmer's Guide to Reserved MS-DOS Functions and Data Structures
  8.           by Andrew Schulman, Raymond J. Michels, Jim Kyle,
  9.               Tim Paterson, David Maxey, and Ralf Brown
  10. (Reading MA: Addison-Wesley, 1990, 694 pp., $39.95, ISBN 0-201-57064-5)
  11.  
  12.         Errata, Commentary, and "Release Notes" - 9 March 1991
  13.                      compiled by Andrew Schulman
  14.                           andrew@pharlap.com
  15.                             CIS 76320,302
  16.                          (617) 661-1510 x238
  17.  
  18.     Here are some corrections and commentary to UNDOCUMENTED DOS.
  19. Thanks to everyone who sent in corrections! (Your help is
  20. acknowledged in the appropriate place below.)
  21.  
  22.     NOTE: If you are having problems reading either of the two disks
  23. that come with UNDOCUDOS, please contact the publisher, Addison-Wesley,
  24. for replacement disks. Call 617-944-3700, and ask for Debby McKenna.
  25.  
  26.  
  27. 11      As an example of the prevalent attitude towards using undocumented
  28.         features, it might have been useful to include a quotation, such
  29.         as "It has been our experience that 'undocumented goodies' are
  30.         interesting to look at, but dangerous to include in software that
  31.         is intended for general distribution" (Paul Yao, _Peter Norton's
  32.         Windows 3.0 Power Programming Techniques_, New York: Bantam Books,
  33.         December 1990, p. 108; this is quite a good book, by the way).
  34.         The best counter-example is Windows 3.0 itself, which (as shown
  35.         on p. 18 of UNDOCUDOS) makes extremely heavy use of 
  36.         undocumented DOS functions; another example is Microsoft's
  37.         Windows debugger CVW, which relies heavily on such undocumented
  38.         Windows functions as WinDebug() and SetEventHook().
  39.  
  40. 16      In the UNDOC.SCR Intrspy script, "DS:SI-byte" and the two
  41.         occurrences of "DS:DX-byte" should read "DS:SI->byte" and
  42.         "DS:DX->byte" (arrow -> not hyphen -).
  43.  
  44. 18      The reference to "DOSSPY" should read "INTRSPY."
  45.  
  46. 19      "used in chapter 5 on TSRs": the DOS SDA is also used extensively
  47.         in chapter 4 on the DOS file system and network redirector.
  48.  
  49. 26      "Get List of List" should of course read "Get List of Lists"
  50.         (by the way, the actual name for this in the MS-DOS source code
  51.         is apparently SYSVARS).
  52.  
  53. 28      The section asserting that 25% of DOS is undocumented was not
  54.         meant to be taken too seriously.
  55.  
  56. 37      The second paragraph should also note that the _asm keyword
  57.         produces the MSC 6 warning "C4204: in-line assembler precludes
  58.         global optimizations."
  59.  
  60. 45      Two readers, Martin Heller (BIX: mheller) and Terrence Vaughn
  61.         (CIS: 72327,2442) found that the assembly-language code at the
  62.         bottom of the page has an incorrect conditional jump and a
  63.         missing label. The block at the bottom of the page should read:
  64.             
  65.                     jne     dos3up      ; DOS 3+    ; CHANGED
  66.                     mov     si, 10h     ; DOS 2.x
  67.                     jmp     short get
  68.             dos3up: cmp     al, 3                   ; CHANGED
  69.                     jne     ofs21
  70.                     and     ah, ah      ; DOS 3.0
  71.  
  72.         The code continues as is on the top of the next page.
  73.  
  74. 47      "Testing simply for equality (for example version >= 4)" is
  75.         confused. Should read "==" not ">=" because we're trying
  76.         to provide an example of what _not_ to do. Testing if
  77.         (version >= 4) is the correct way to do it; testing if
  78.         (version == 4) is the wrong way to do it.  (This correction
  79.         is probably more confusing than our original typographical
  80.         error!)
  81.  
  82. 65      Four lines from the bottom, "interrupt handle" should read
  83.         "interrupt handler."
  84.  
  85. 70      "Programs written for Microsoft Windows 3.0 can make
  86.         undocumented DOS calls without any special handling. This
  87.         includes Windows 3.0 running in 386 enhanced mode."
  88.  
  89.         Ahem! Boy, is this statement wrong. I had tested only one
  90.         undocumented DOS call from a Windows program when I wrote
  91.         that. It was the LASTDRIVE check from Chapter 2, where 
  92.         INT 21h AH=52h is called, and then the LASTDRIVE byte is
  93.         read out of the List of Lists.
  94.  
  95.         It turns out, this _just happened_ to work, but I should still
  96.         have known better than to make this stupid claim. Windows
  97.         3.0 programs in Standard and Enhanced modes are running in
  98.         protected mode (which is why Windows is finally a usable
  99.         product). Thus, the same restrictions noted elsewhere in Chapter
  100.         2 for making undocumented DOS calls from protected mode apply
  101.         to Windows programs as well. The LASTDRIVE check just happened
  102.         to work, because INT 21h AH=52h seems to be transparently
  103.         supported in protected mode (this can't be relied on, however)
  104.         and because reading the LASTDRIVE byte out of the List of Lists
  105.         doesn't involve any further pointer dereferencing. For example,
  106.         trying to walk the DOS device chain or MCB chain would _not_
  107.         have worked; other undocumented DOS calls (e.g., INT 21h
  108.         AX=5D06h) are not supported at all in protected mode.
  109.  
  110.         In order to make undocumented DOS calls from a Windows program
  111.         running in Standard or Enhanced mode (the only modes that matter),
  112.         you must use the DOS Protected Mode Interface (DPMI) function
  113.         to "Simulate Real Mode Interrupt" (INT 31h AX=0300h). In almost
  114.         all cases, you will then need to map one or more real-mode
  115.         pointers into your protected-mode address space. This can be 
  116.         done using either DPMI (the Allocate LDT Descriptors [INT 31h
  117.         AX=0000h], Get Descriptor [INT 31h AX=000Bh] and Set Descriptor
  118.         [INT 31h AX=000Ch] calls) or using the Windows AllocSelector()
  119.         call together with -- ta da! -- some undocumented Windows calls
  120.         (which will be the subject of a forthcoming book by A. Schulman,
  121.         D. Maxey, et al., titled _Undocumented Windows Programming_):
  122.  
  123.         /* a few useful undocumented KERNEL functions */
  124.         extern DWORD FAR PASCAL GetSelectorBase(unsigned sel);
  125.         extern DWORD FAR PASCAL GetSelectorLimit(unsigned sel);
  126.         extern void FAR PASCAL SetSelectorBase(unsigned sel, DWORD base);
  127.         extern void FAR PASCAL SetSelectorLimit(unsigned sel, DWORD limit);
  128.  
  129.         In other cases (for example, INT 21h AH=60h), you will need to 
  130.         allocate a conventional-memory buffer for use by an undocumented
  131.         DOS call. Use the Windows GlobalDosAlloc() call for that.
  132.  
  133.         In any case, the statement that Windows programs "can make
  134.         undocumented DOS calls without any special handling" couldn't
  135.         be further from the truth. The above notes should get you
  136.         started. In addition, a forthcoming PC MAGAZINE Lab Notes
  137.         by A. Schulman (tentatively titled "Moving DOS Programs to
  138.         Windows with DPMI") will contain further information on this
  139.         topic, as will a forthcoming book by A. Schulman and D. Maxey, 
  140.         tentatively titled _The DOS Programmer's Guide to Microsoft
  141.         Windows_ (Addison-Wesley, due December 1991).
  142.  
  143.         Those interested in exploring the innards of Windows might
  144.         want to check out Alan Cobb's pamphlet _Reverse Engineering
  145.         Windows and OS/2 Software_. Contact Alan at CIS 73170,3543
  146.         or BIX/MCI (AlanCobb).
  147.  
  148. 73      Discussion of Phar Lap 386|DOS-Extender: "those weird calls (like
  149.         undocumented DOS) not transparently supported in protected mode."
  150.         In 386|DOS-Extender 3.0, many undocumented DOS calls _are_
  151.         supported in protected mode, so this statement is no longer
  152.         quite accurate. For example, INT 21h AH=52h returns in ES:EBX
  153.         a pointer to the List of Lists. However, any far pointers
  154.         in the data structure remain real-mode far pointers, so these
  155.         would need special handling.
  156.  
  157.         The LASTDRIVE example is inadequate to bring out the issue
  158.         of using far pointers in the List of Lists. In Windows 3.0
  159.         protected mode, LASTDRIVE just happened to work. The same
  160.         thing would happen in 386|DOS-Extender 3.0. In the second 
  161.         edition of UNDOCUDOS, we will need an additional example
  162.         for these environments.
  163.  
  164.         Also note that Phar Lap now has a second product, 
  165.         286|DOS-Extender, with a different API from our 386
  166.         product. In 286|DOS-Extender, you would call DosRealIntr()
  167.         to issue an INT 21h AH=52h, and then call DosMapRealSeg()
  168.         to map any real-mode pointers into your program's
  169.         protected-mode address space. Several programs from
  170.         UNDOCUDOS were ported to protected mode using 
  171.         286|DOS-Extender, and appear in its _Developer's Guide_.
  172.         
  173. 74      the parenthetical remark should read: "(note that INT 2Fh
  174.         AH=16h and AH=17h are the Microsoft Windows interface
  175.         for non-Windows applications; for more information, see
  176.         the INTRLIST.EXE database on disk)."  A detailed discussion
  177.         of the Windows INT 2Fh functions will appear in the
  178.         forthcoming book by Schulman and Maxey, noted earlier.
  179.  
  180. 75      The structures at the top of the page are wrong. A better one
  181.         is:
  182.  
  183.             /* structure of a protected-mode descriptor */  
  184.             typedef struct {
  185.                 unsigned limit, addr_lo;
  186.                 unsigned char addr_hi, access, reserved, addr_xhi;
  187.                 } DESCRIPTOR;   
  188.  
  189.         If you still want the access-rights byte to use an ACCESS
  190.         bit field, rather than a plain unsigned char, then you must
  191.         ensure that the bit field occupies only one byte. Microsoft
  192.         C allows the following non-standard use of unsigned char
  193.         in a bit field, so:
  194.  
  195.             typedef struct {
  196.                 unsigned char accessed   : 1;
  197.                 unsigned char read_write : 1;
  198.                 unsigned char conf_exp   : 1;
  199.                 unsigned char code       : 1;
  200.                 unsigned char xsystem    : 1;
  201.                 unsigned char dpl        : 2;
  202.                 unsigned char present    : 1;
  203.                 } ACCESS;
  204.  
  205.         An improved version of LDDPMI.C appears at the end of these
  206.         release notes. (The improved version now works with Borland
  207.         C++ 2.0, as well as with Microsoft C 6.0 and higher.)
  208.  
  209. 76      In dpmi_init(), after the call to _dos_allocmem(), the
  210.         following line of code should be added:
  211.  
  212.             dpmi_flags &= ~1;  // this is a 16-bit protected-mode program
  213.  
  214.         When INT 2Fh AH=1687h returns the DPMI flags in BX, the bottom
  215.         bit (dpmi_flags & 1) indicates whether the DPMI host supports
  216.         32-bit programs (Windows 3.0 enhanced mode does). But when
  217.         passing flags back into the "Real to Protected Mode
  218.         Switch Entry Point" via (*dpmi)(), the bottom bit of the
  219.         flags passed in AX indicates whether _this_ is a 32-bit
  220.         program. LDDPMI is a 16-bit program, so the bit must be turned
  221.         off.
  222.  
  223. 79      Before the call to dpmi_set_descriptor(), it would have been
  224.         much simpler to call a dpmi_get_descriptor() function for some
  225.         known selector (like the program's DS), and then just change
  226.         the appropriate base-address and limit fields. 
  227.  
  228. 80      "LDDPMI uses functions such as pmode_printf() rather than
  229.         plain old printf().... Most DPMI servers will in fact 
  230.         provide protected-mode INT 21h services (the Windows 3.x
  231.         DOS extender does, for example), but that is a facility
  232.         provided by the DPMI server, not by DPMI itself."
  233.  
  234.         This is literally correct, but it still was foolish not to
  235.         go ahead and use plain old printf(), since any DPMI
  236.         server will provide the necessary underlying INT 21h services
  237.         in protected mode. For example, see the program HELLOPMW.C
  238.         in Ray Duncan, "An Introduction to the DOS Protected Mode
  239.         Interface," _PC Magazine_, 12 February 1991, p. 370. 
  240.         (Duncan's three-part series on DPMI in _PC Magazine_,
  241.         12 February 1991, 26 February 1991, and 12 March 1991, is
  242.         an excellent introduction to the topic.)
  243.  
  244.         In fact, this could have been used as an opportunity to
  245.         explore yet another semi-undocumented aspect of Windows,
  246.         since the fact that Windows provides INT 21h services
  247.         in protected mode is kept pretty well hidden (talk about
  248.         hiding your light under a bushel!). The only documentation
  249.         is a brief (five-page) document titled "Windows INT 21H and
  250.         NetBIOS Support for DPMI," which is included in a packet of
  251.         Microsoft Windows development notes (Part No. 050-030-313). 
  252.         (Note also that many more DPMI calls are actually supported
  253.         by Windows than this document indicates.)
  254.  
  255. 88      "INT 4Bh is used for 'DMA Services'": the actual title of
  256.         the specification is "Virtual DMA Services (VDS), and is
  257.         available as Microsoft Part No. 098-10869. It is also
  258.         supported by Windows 3.0 Enhanced mode and 386MAX.
  259.  
  260. 89      change "is still 138,000 bytes" to "are still 138,000 bytes"
  261.  
  262. 90      It is useful to add one line of code to function walk(), so
  263.         that the ending address of the MCB chain (usually A000h) is
  264.         displayed. Change the case 'Z' block so that it reads:
  265.  
  266.             case 'Z' : /* Zbikowski : end of MCB chain */
  267.                 display(mcb);
  268.                 printf("%04X\n", FP_SEG(mcb) + mcb->size + 1);
  269.                 return;
  270.  
  271. 98-9    Ralf Brown (ralf@cs.cmu.edu) points out that INT F0h or so
  272.         through FFh contain garbage which appears to pointing into
  273.         one of the currently loaded programs, because the BIOS uses
  274.         the upper end of the interrupt vector table as a stack
  275.         during bootup.
  276.  
  277. 100     The assertion that the C free() function, or the Pascal
  278.         dispose() function, indirectly uses the DOS memory functions
  279.         isn't quite true. Calls like free() or dispose() don't call
  280.         INT 21h Function 49h (Release Memory Block); instead, they
  281.         simply put freed memory blocks back on a free list. To actually
  282.         release freed memory blocks back to the operating system, you
  283.         need to use a function like _heapmin() in Microsoft C 6.0 or
  284.         mark()/release() in Turbo Pascal.
  285.  
  286. 102     "and will stop searching": Ralf Brown states that this is not
  287.         true. As it turns out, all three allocation strategies search
  288.         the entire memory chain. Any search sets all three memory-block
  289.         variables for all three strategies in the DOS SDA (at offsets
  290.         1Eh, 20h, and 22h; see UNDOCUDOS, pp. 551, 557), and then
  291.         returns the appropriate one.
  292.  
  293. 111     "COMMAND.COM is always its own parent, and so..." Fine, but
  294.         we never explain _why_ COMMAND.COM is always its own
  295.         parent. Here's a good explanation from BIX:
  296.  
  297.         ibm.dos/secrets.3 #1106, from drifkind, 1510 chars, 
  298.         Fri Jan 25 20:26:19 1991
  299.         --------------------------
  300.         TITLE: Zombie COMMAND.COM rises from dead
  301.  
  302.         (I'm practicing to write headlines for the National Enquirer.)
  303.  
  304.         COMMAND.COM contains the default INT 24h handler, the one that
  305.         prints "Abort, Retry, Ignore" and so on.  What happens when a
  306.         critical error occurs while executing an internal DOS command?
  307.         Obviously, the INT 24h handler knows that COMMAND.COM is running
  308.         and does something other than abort if you press "A", right?
  309.  
  310.         No, in fact the critical error handler does nothing special.  If
  311.         you press "A", it returns 2 and DOS terminates the current
  312.         process.  So why doesn't COMMAND.COM go away?
  313.  
  314.         When DOS terminates a process, it uses the "parent PID" field in
  315.         the process's PSP to figure out what process is going to get
  316.         control when this one terminates.  If the parent PID is the same
  317.         as the current PID, however, it does not deallocate the program's
  318.         memory blocks before exiting.  COMMAND.COM sets the parent PID
  319.         field equal to its own PID, and points the termination address
  320.         (at offset 0Ah in the PSP) back into itself.  The result is that,
  321.         on exit, the current program stays active and retains control.
  322.  
  323.         We all know that if you run COMMAND.COM with the "/p" switch, it
  324.         does not terminate when you type "exit".  In fact, it DOES exit.
  325.         The difference is that, with "/p", it does not restore the
  326.         original parent PID and termination addresses, so DOS more or
  327.         less ignores the 4Ch service request, just transferring control
  328.         back into COMMAND.COM.
  329.  
  330.         And that is why COMMAND.COM's parent PID field points to itself.
  331.  
  332. 122-4   The DEVCON program has been ported to protected mode: see
  333.         Phar Lap 286|DOS-Extender _Developer's Guide_, pp. 159-165.
  334.  
  335. 125     Change "if you run DEVCON a dash" to "if you run DEVCON with
  336.         a dash".
  337.  
  338. 125     Change "it's often forgetten" to "it's often forgotten".
  339.  
  340. 137     The block of code in while (CmdPkt.nunits--), before the
  341.         call to INT 21h AH=53h, should somewhere explain what we're
  342.         doing:
  343.  
  344.             _ES = CmdPkt.brkseg;  /* DS:SI -> BIOS Parameter Block */
  345.             _DS = CmdPkt.inpseg;  /* ES:BP -> Disk Parameter Block */
  346.  
  347. 139     "The function copyptr()... could have been written in C,
  348.         but doing so would have required the kind of convoluted
  349.         expressions that have given C the reputation of being a
  350.         'write-only' language."  Well, I don't know, but the following
  351.         looks pretty simple to me:
  352.  
  353.             typedef void far *FP;
  354.             void copyptr (FP far *src, FP far *dst) { *dst = *src; }
  355.  
  356. 156     "Not long after that (but before the introduction of DOS
  357.         2.0), an extra sector was added to the format, bringing the
  358.         storage capacity up to the 360KB we know today."  In fact,
  359.         Tim Paterson assures us that was done in DOS 2.0.
  360.  
  361. 157     Re: the mentions of S=0, drifkin (BIX) points out that normal
  362.         PC block devices number physical sectors starting with 1,
  363.         not 0. Change 'em all to S=1.
  364.  
  365. 158     Change "major reasons many users to upgrade" to "major reasons
  366.         for...".
  367.  
  368. 158     "Two copies of the FAT are normally maintained by DOS, but
  369.         no real reason for doing so has been determined." This was
  370.         a pretty foolish remark, considering that Tim Paterson is 
  371.         one of the coauthors, and we only needed to ask him why he
  372.         did this! The answer is that DOS maintains two copies of
  373.         the FAT is case of _physical_ disk errors; Microsoft used
  374.         three FATs in standalone BASIC, and this is where the
  375.         idea of using multiple FATs came from.
  376.  
  377. 158     On the last line, the phrase "together with a flag bit
  378.         in the format records" is, as Peter Schultz (CIS 70216,074)
  379.         pointed out, rather vague. Jim Kyle explains that it really
  380.         isn't a "flag bit," but rather the top four bits in the
  381.         DPB highest-cluster word (DPB offset 0Dh; see UNDOCUDOS, 
  382.         p. 507). Checking these bits is preferable to using the boot
  383.         record.
  384.  
  385. 159     "Any other value indicates..." In fact, drifkind (BIX) 
  386.         cautions us not to forget about (F)FF7, which marks bad
  387.         clusters, and (F)FF0 through (F)FF6, which are reserved.
  388.  
  389. 160     "E5h, which is a valid character for use in a filename":
  390.         it's valid in DOS 3+.
  391.  
  392. 160     "If the first byte of the filename is E5h..." Why E5h?
  393.         Tim Paterson says because 8" SSSD disks came preformatted
  394.         with E5h bytes. A disk out of the box thus looked empty,
  395.         and was essentially ready-to-go, though the FAT still 
  396.         needed to be cleared (which was done with the built-in
  397.         CLEAR command in pre-IBM DOS).
  398.  
  399. 161     FAKEFRMT: roedy (BIX) points out that this utility "will
  400.         inadvertently bring bad tracks back into active duty."
  401.         Tim Paterson states that FAKEFRMT shouldn't need to
  402.         rewrite the boot sector. These issues will be taken up
  403.         in the second edition.
  404.  
  405. 161     "(Byte 2 of the sector for a 12-bit FAT)" should refer
  406.         instead to Byte 3. (Another catch by Ralf Brown.)
  407.  
  408. 166     LoL+10h: The last sentence on the page asserts that "if
  409.         larger, this value is replaced by the new maximum value."
  410.         Ralf Brown, in yet another catch, points out that LoL+10h
  411.         is actually increased only for the built-in device drivers
  412.         located in IO.SYS; if the driver's value is greater than
  413.         LoL+10h for installable drivers, the loader complains that
  414.         the sector size is too large.
  415.  
  416. 168     "(available directory in 4+" should read "(available directly
  417.         in 4+".
  418.  
  419. 176     To the phrase "This means that all the named devices seem to
  420.         exist in all directories of the file system," add the
  421.         parenthetical remark that they also exist in subdirectory \DEV,
  422.         even if no such subdirectory exists on disk.
  423.  
  424. 179     "it differed the data thatwas" should read "it differed from
  425.         the data that was" (two mistakes!; who the #$%*& edited this
  426.         stuff?!).
  427.  
  428. 182     "Local Description Table" should of course read "Local Descriptor
  429.         Table".
  430.  
  431. 186     "file stem" should read "file system".
  432.  
  433. 190-1   The TRUENAME program has been ported to protected mode: see
  434.         Phar Lap 286|DOS-Extender _Developer's Guide_, pp. 86-92.
  435.  
  436. 191     The variable "s" serves no purpose in main(), and in fact
  437.         could potentially cause a problem (ya see it?). Change main()
  438.         to the following:
  439.  
  440.             main(int argc, char *argv[])
  441.             {
  442.                 char buf[128];
  443.                 if (argc < 2)
  444.                     ret("usage: dospath <filename>", 1);
  445.                 if (_osmajor < 3)
  446.                     ret("requires DOS 3.0 or greater", 1);
  447.  
  448.                 if (truename(argv[1], buf))
  449.                     ret(buf, 0);
  450.                 else
  451.                     ret("invalid filename", 1);
  452.             }
  453.  
  454. 193     "The first SFT appears to always hold five possible open-file
  455.         entries": Ralf Brown explains that this is because the first
  456.         SFT is compiled right into MSDOS.SYS, for DOS 2.0 through 4.0.
  457.             
  458. 197     Neil Rubenking (CIS 72241,50) found that struct file didn't
  459.         work under DOS 3.0. See corrections for p. 527 below for
  460.         the correct SFT structure for DOS 3.0. 
  461.  
  462. 198     In the function is_psp() the magic number 0x20CD is never
  463.         explained. This is merely the opcode for the INT 20h
  464.         instruction, interpreted as an unsigned quantity.
  465.  
  466. 199     The test "FP int2e = (FP) GETVECT(0x2E)" will of course fail
  467.         if COMMAND.COM, or a program that mimicks COMMAND.COM's use
  468.         of INT 2Eh, is not present. For example, what happens when
  469.         the user is running SH.EXE from the MKS Toolkit as their
  470.         DOS SHELL=?
  471.  
  472. 199     The IS_AUX(), IS_CON(), and IS_PRN() macros are all missing
  473.         a test for (s[3] == ' '). Otherwise, we would match possible
  474.         device names such as "AUXIL", "CONTOUR", and "PRNACHO".
  475.  
  476. 210-3   "More File Handles": After UNDOCUDOS was already out, a 
  477.         useful article on this topic appeared: David Burki, "DOS
  478.         File Handle Limits," _TECH Specialist_, February 1991,
  479.         pp. 51-62.
  480.  
  481. 212     Rather than fail if (new_max > files()), it probably would
  482.         have been a good idea to show how to grow the SFT tables,
  483.         a la Quarterdeck's FILES.COM program (included with QEMM).
  484.  
  485. 213     FHANDLE.C is an okay program, but it needs to show things
  486.         more from a C perspective. We at least need to explain why
  487.         we use _dos_open() and not open() or fopen(). We need to
  488.         explain why increasing the number of DOS file handles
  489.         doesn't give your C program more FILE* capacity.
  490.         This is an incredibly common question. For now, if
  491.         you do need to increase the number of FILE* in your Microsoft
  492.         C program, note that _NFILE can be changed in the startup
  493.         code (see MSC6 STARTUP\CRT0DAT.ASM).
  494.  
  495. 214-5   Roger Jackson (CIS 76535,75) points out that MOV.C doesn't
  496.         compile with Microsoft C. The problem is that FP_SEG()
  497.         and FP_OFF() as used here depend on the Turbo C++ style of
  498.         these macros. As noted in UNDOCUDOS p. 51, Microsoft C's version
  499.         of these macros requires an lvalue. To fix MOV.C for MSC,
  500.         change the two blocks of FP_SEG/FP_OFF code:
  501.  
  502.             void canonicalize(filespec,canonical,errorlevel)
  503.             // ...
  504.             void far *lvalue;
  505.             regs.h.ah = 0x60 ;
  506.             lvalue = filespec;
  507.             regs.x.si = FP_OFF(lvalue) ;
  508.             segregs.ds = FP_SEG(lvalue) ;
  509.             lvalue = canonical;
  510.             regs.x.di = FP_OFF(lvalue) ;
  511.             segregs.es = FP_SEG(lvalue) ;
  512.             // ...
  513.  
  514.             void far *lvalue;
  515.             dpl.ax = 0x5600 ;   /* indirect function is rename */
  516.             lvalue = &source;
  517.             dpl.dx = FP_OFF(lvalue) ;
  518.             dpl.ds = FP_SEG(lvalue) ; /* DS:DX old filespec */
  519.             lvalue = ⌖
  520.             dpl.di = FP_OFF(lvalue) ;
  521.             dpl.es = FP_SEG(lvalue) ; /* ES:DI new filespec */
  522.  
  523. 229-30  Subfunctions 0Eh, 0Fh, 11h, 13h, and 17h: for each, add an
  524.         additonal required input:
  525.             
  526.             SDA.CURR_CDS = Current Directory Structure (CDS) for
  527.                 drive with file
  528.  
  529.         The entries for these subfunctions are already correct in
  530.         the appendix to UNDOCUDOS, pp. 607-612.
  531.  
  532. 257     The usage message should point out that PHANTOM -u uninstalls
  533.         the Phantom drive.
  534.  
  535. 276-7   Tim Paterson pointed out that the "; Microsoft C 6.0 only"
  536.         comment next to MOV SP, BP (which appears once on each page) 
  537.         is pretty confused. First of all, the MOV SP, BP must of
  538.         course be balanced with the earlier MOV BP, SP. Second of 
  539.         all, this optional save/restore of the stack pointer has
  540.         to do merely with whether the compiler uses the stack or
  541.         a register for the variable i. This has nothing to do with 
  542.         MSC 6.0. Thus, the comment should be removed. 
  543.  
  544. 277     "the large amount of space used for our three-line
  545.         interrupt handler should go unnoticed." This was supposed to
  546.         say "should _not_ go unnoticed." See S. Freud, _Psychopathology
  547.         of Everyday Life_, for further details.
  548.  
  549. 280     "When coding in assembly language, you can easily come up
  550.         with this number..." Perhaps for .COM files, but certainly
  551.         for .EXE TSRs, E. Nicholas Cupery (CIS 72657,3646) points
  552.         out that the number is _not_ so easy to come up with!
  553.         In fact, our statement was a pure "exercise left for the
  554.         reader" cop-out (that is, we didn't know the answer either,
  555.         so we just pretended it was a trivial operation).
  556.  
  557.         Ted Mirecki (CIS 72631,25; author of the wonderful
  558.         "Tech Notebook" series in the defunct _PC Tech Journal_),
  559.         responding to comments by Cupery and Mike R. Lovett
  560.         (CIS 72361,3715), made the following suggestion for deriving
  561.         the memory footprint of a multi-segment TSR:
  562.  
  563.         "Instead of calculating the size of each segment & adding
  564.         the sizes together, calc the parag address of each segment end and
  565.         select the highest one. Then subtract the parag address of the PSP, 
  566.         and voila, you have the number of parags taken up by your program.
  567.  
  568.         "Say the label of the end of a particular segment is ENDLBL.
  569.         Then to get its parag address, do the following steps:
  570.         Add 15 to offset of ENDLBL   (rounds up to next parag boundary)
  571.         Shift it right 4 bits        (gets # of parags in segment)
  572.         Add to Segment of ENDLBL     (gives parag addr of end of seg).
  573.  
  574.         "You can either repeat this for all your segs & choose the highest, 
  575.         or arrange for the segments to be loaded in some particular order
  576.         & do it only for the topmost one. 
  577.  
  578.         "Then get the PSP segment and subtract it from the above."
  579.  
  580. 280     Jerry Watkins (CIS 70521,2401) points out the MSC memory map
  581.         isn't quite right. In DGROUP, the stack appears lower in
  582.         memory than the near heap. (In your copy of UNDOCUDOS, just
  583.         swap the two lines "STACK" and "NEAR HEAP".) 
  584.  
  585. 296     "need to examine is that one that" should read "need to examine
  586.         is the one that".
  587.  
  588. 320     Several readers have noted that the discussion of the INT 2Ah
  589.         AH=8xh critical-section functions is skimpy. This will be
  590.         beefed-up either in a future "Release Notes" for the book, 
  591.         or at least in the second edition. Jack Brennan
  592.         (John.Brennan@vi.ri.cmu.edu) made the following comments on
  593.         INT 2Ah Functions 80h/81h:
  594.  
  595.         "These calls are only made by the DOS kernel if they are enabled
  596.         by some rather specialized code. All network redirectors that I
  597.         have seen enable the calls, as does Windows 3.0 in 386 Enhanced
  598.         mode (but not in Real or Standard mode). Basically, what needs to
  599.         be done is as follows:
  600.  
  601.         "1. Find a table of offsets at location 02C3 in the IBMDOS segment.
  602.         This loc (02C3) is hard-coded into MSREDIR, the Microsoft Net
  603.         Redirector. The table is zero terminated.
  604.  
  605.         "2. For each offset in the table, poke the hex value 50 into the byte
  606.         at IBMDOS:offset. This replaces a RET instruction with a PUSH AX
  607.         instruction, allowing the applicable subroutines to execute.
  608.  
  609.         "I am only certain of this with DOS 3.1 to 3.31.
  610.  
  611.         "I believe that this enabling code would need to be added to the
  612.         example TSR in Chapter 5 of Undocumented DOS (TSREXAMP.C) in order 
  613.         to allow the TSR to be robust in a non-networked, non-Windows,
  614.         non-MSCDEX, etc., environment (assuming compiling with DOS_SWAP
  615.         enabled)."
  616.  
  617. 322     The test at the bottom of the page, if (dos_level == 4), is
  618.         confusing, or will be when DOS 5 comes out. Note that at
  619.         the beginning of the function, we set:
  620.  
  621.             else if (_osmajor >= 4)
  622.                 dos_level == 4;
  623.                 
  624.         This is rather confusing. In any case, this code should work
  625.         with DOS 5.
  626.  
  627. 325     In the paragraph at the bottom of the page, "(unless, of
  628.         course, a critical section has been flagged via INT 2Fh)"
  629.         should of course read "via INT 2Ah."
  630.  
  631. 328     The subhead "TSFILE" should read "TSRFILE".
  632.  
  633. 332     "which we can be used" should read "which can be used".
  634.  
  635. 380     In TSHELL.C, it looks as if TSHELL passes an improperly
  636.         formatted argument list to COMMAND.COM, but Jim Kyle swears
  637.         up and down that the code works; it's too tricky, but it's
  638.         not wrong.
  639.  
  640. 393     "find what is called the 'active' environment, _not_ the
  641.         master environment." Actually, it seems we made things more
  642.         difficult than necessary. In fact, the "active" environment
  643.         is most of the time the one you want. For example, ENVEDT,
  644.         which works off the master environment, doesn't change the
  645.         correct environment when running under Windows.
  646.  
  647. 398     "For example, if a .BAT file containing a SET statement is
  648.         compiled with BAT2EXEC, it fails unexpectedly under this
  649.         situation producing an 'Out of environment space' message."
  650.         Doug Boling, author of BAT2EXEC (which appeared in _PC Magazine_, 
  651.         August 1990), thinks we probably had an old copy of BAT2EXEC.
  652.         The latest versions of _PC Magazine_ utilities can be downloaded
  653.         from PCMagnet on CompuServe.
  654.  
  655. 405     The discussion of INT 2Eh should note that the real "meat" on
  656.         this weird aspect of undocumented DOS can be found in Daniel
  657.         E. Greenberg, "Reentering the DOS Shell," _Programmer's
  658.         Journal_, May-June 1990, pp. 28-36. This article is the
  659.         definitive piece on INT 2Eh.
  660.  
  661. 408     In the comments to TEST2E.C, the Microsoft C compilation
  662.         instructions should refer to SEND2E.C, not "send2e.asm".
  663.  
  664. 409     We need to discuss the issue of running INT 2Eh "clients" like
  665.         TEST2E.EXE from within a batch file. Michael Mefford ("Running
  666.         Programs Painlessly," _PC Magazine_, 16 February 1988) claims
  667.         that programs using INT 2Eh "will not execute batch files nor
  668.         work from within a batch file." Jeff Prosise, in a good recent
  669.         article on undocumented DOS ("Undocumented DOS
  670.         Functions," _PC Magazine_, 12 February 1991) states: "Be careful
  671.         about how you call interrupt 2Eh. If you aren't, you can crash
  672.         your system in certain very common situations. The main one if
  673.         if the program you're using is running under a batch file. 
  674.         Since INT 2Eh is nonreentrant, DOS uses it to run batch files.
  675.         So if you run a batch file using INT 2Eh from your program,
  676.         your system will crash."
  677.  
  678.         We too have had problems running programs that use INT 2Eh
  679.         from within a batch file, but nothing so dramatic as crashing
  680.         the system. Instead, we have found simply that EXIT is not
  681.         handled properly and that memory can be lost. In any case,
  682.         the use (or refraining from use!) of INT 2Eh in a batch
  683.         file needs further discussion in the next edition of UNDOCUDOS.
  684.  
  685. 449     "protected-mode debugging requires an interface more like
  686.         that of OS/2's DosPTrace()." At the time, this was just a wild
  687.         guess. It turned out to be true. If you have the Windows SDK,
  688.         run EXEHDR \WINDOWS\SYSTEM\WINDEBUG.DLL, and you'll find
  689.         the description "Ptrace for Windows." In fact, the undocumented
  690.         WinDebug() function is nearly identical to the poorly-
  691.         documented DosPTrace() function in OS/2. Microsoft claims
  692.         that WinDebug() will completely go away in Windows 3.1, to be
  693.         replaced by a openly-documented new interface. In the meantime,
  694.         and perhaps for some time even after 3.1 is released, WinDebug()
  695.         is quite important.
  696.  
  697. 453     At the bottom of the page, the phrase "as noted below, INTRSPY
  698.         also uses -> to indicate fields in a structure" should be
  699.         deleted; it's not true. INTRSPY uses -> solely to indicate
  700.         that a register pair should be treated as a pointer to some
  701.         type. (For example, ds:dx->byte,asciiz,64.)
  702.  
  703. 458     At the bottom of the page, "The following command used to
  704.         run CMDSPY.EXE" should refer instead to INTRSPY.EXE.
  705.  
  706. 466     "or it parameters replaceable from the DOS command line":
  707.         delete the word "it."
  708.  
  709. 482     "that function is not available provided by the Compaq ROM
  710.         BIOS": delete the word "available."
  711.  
  712. 495     Dan Lanciani's name was unfortunately omitted from the list
  713.         of major contributors of undocumented-DOS material to the
  714.         Interrupt List maintained by Ralf Brown. Dan contributed
  715.         INT 21h and INT 2Fh material. Sorry, Dan.
  716.  
  717. 502-4   History buffs may want to replace the vague note "appears to
  718.         be for CP/M compatibility" for Functions 18h, 1Dh, 1Eh, and 20h
  719.         with the actual names of the CP/M-80 and CP/M-86 functions for which
  720.         holes were apparently left:
  721.             18h:    Get Bit Map of Active Drives
  722.             1Dh:    Get Bit Map of Read-Only Drives
  723.             1Eh:    Set File Attributes
  724.             20h:    Get/Set User (Sublibrary) Number
  725.         (See David Cortesi, "CP/M-86 vs. MS-DOS: A Technical Comparison,"
  726.         _Dr. Dobb's Journal_, July 1982, pp. 14-27; in DDJ Vol. 7, 
  727.         pp. 280-291. The article notes that "MSDOS was commissioned by
  728.         IBM and produced by the Microsoft Corporation from a base
  729.         written by Seattle Computer Products, Inc. Lifeboat Associates,
  730.         an important software vendor, has undertaken to market the
  731.         system for other 8086-based machines, and to encourage the
  732.         development of application programs for it." Gee, wonder if 
  733.         it'll be successful....)
  734.  
  735. 513     21/4B/03:  Bob Moote of Phar Lap Software (rwm@pharlap.com) has
  736.         reported a bug in INT 21h Function 4Bh Subfunction 03h (Load
  737.         Overlay). If there is additional data located in the file after
  738.         the program (i.e., 21/4B/03 does not hit EOF; e.g., a program
  739.         with a bound-in DOS extender), Load Overlay will load up to 512
  740.         extra bytes, overflowing your buffer.
  741.  
  742. 514     The note "DOS 2.x destroys all registers" should be amended to
  743.         note that in DOS 3+, the BX and DX registers are still bashed.
  744.         (This is noted correctly on p. 429 of UNDOCUDOS.)
  745.  
  746. 527     Neil Rubenking found that the structure provided for the SFT
  747.         did not work in DOS 3.0. Robin Walker (RDHW@phoenix.cambridge.edu.uk)
  748.         supplied the correct information.  Thanks, Robin!!  Here 'tis:
  749.  
  750. Format of DOS 3.0 system file tables and FCB tables:
  751.  
  752. Offset  Size    Description
  753.  00h    DWORD   pointer to next file table
  754.  04h    WORD    number of files in this table
  755.  06h    38h bytes per file                   *** NB ***
  756.  
  757.         Offset  Size    Description
  758.  
  759.         00h-1Eh as for DOS 3.1+
  760.  
  761.         1Fh     WORD    byte offset of directory entry within sector ** NB **
  762.         21h  11 BYTES   filename in FCB format (no path/period, blank-padded)
  763.         2Ch     DWORD   (if SHARE loaded) ptr to prev SFT sharing same file
  764.         30h     WORD    (if SHARE loaded) ??? network machine number, I guess
  765.         32h     WORD    (if SHARE loaded) PSP segment of file's owner
  766.         34h     WORD    (if SHARE loaded) offs in SHARE code seg of share rec
  767.         36h     WORD    ??? only seen 0000h         *** NB ***
  768.  
  769. The format of sharing records looks the same as already listed.
  770.  
  771. 544-5   Has anyone found what 2F/57/02, 2F/57/03, and 2F/57/04 do?
  772.         A reader needs this information!  If you know anything, please
  773.         contact andrew@pharlap.com.
  774.  
  775. 581-2   2A/80, 2A/81: Please see the notes above (p.320).
  776.  
  777. 597     2F/10/00: Note that DOS 4.01 loads share for media >32M, but
  778.         only for FCB support. The file-sharing code is not turned on
  779.         until the first call to 2F/10/00. Also note that Microsoft
  780.         Windows 3.0 enhanced mode provides its own built-in implementation
  781.         of SHARE.
  782.  
  783. ;----------------------------------------------------------------------
  784.  
  785. /*
  786. LDDPMI.C -- undocumented DOS call from DPMI
  787.  
  788. Revised substantially from the version in UNDOCUMENTED DOS, pp. 74-80
  789.  
  790. Works with Microsoft C 6.0 (or higher) and Borland C++ 2.0 (or higher)
  791. (Some of the _asm convolutions were needed for Borland C++)
  792.  
  793. sample output:
  794.     in protected mode
  795.     Real mode DOS List Of Lists = 028E:0026
  796.     Protected DOS List Of Lists = 00AD:0026
  797.     LASTDRIVE=E
  798.         
  799. Microsoft C 6.0 (or higher): cl -AS lddpmi.c     
  800. Borland C++ 2.0 (or higher): bcc -ms lddpmi.c
  801. */
  802.  
  803. #include <stdlib.h>
  804. #include <stdarg.h>
  805. #include <stdio.h>
  806. #include <assert.h>
  807. #include <dos.h>
  808.  
  809. #ifdef __TURBOC__
  810. #pragma inline
  811. #define _dos_allocmem(x,y)      (allocmem(x, y) != -1)
  812. #endif
  813.  
  814. #define ABSADDR(seg, ofs) \
  815.     ((((unsigned long) seg) << 4) + ((ofs) & 0xFFFF))
  816.  
  817. #pragma pack(1)
  818.  
  819. typedef struct {
  820.     unsigned long edi, esi, ebp, reserved, ebx, edx, ecx, eax;
  821.     unsigned flags, es, ds, fs, gs, ip, cs, sp, ss;
  822.     } RMODE_CALL;
  823.     
  824. typedef struct {
  825.     unsigned char accessed   : 1;
  826.     unsigned char read_write : 1;
  827.     unsigned char conf_exp   : 1;
  828.     unsigned char code       : 1;
  829.     unsigned char xsystem    : 1;
  830.     unsigned char dpl        : 2;
  831.     unsigned char present    : 1;
  832.     } ACCESS;
  833.     
  834. /* structure of a protected-mode descriptor */  
  835. typedef struct {
  836.     unsigned limit, addr_lo;
  837.     unsigned char addr_hi;
  838.     ACCESS access;
  839.     unsigned char reserved, addr_xhi;
  840.     } DESCRIPTOR;   
  841.     
  842. typedef enum { FALSE, TRUE } BOOL;
  843.  
  844. BOOL dpmi_rmode_intr(unsigned intno, unsigned flags, 
  845.     unsigned copywords, RMODE_CALL far *rmode_call);
  846.  
  847. void dos_exit(unsigned char err)
  848.     _asm mov al, err
  849.     _asm mov ah, 04ch
  850.     _asm int 21h
  851. }
  852.  
  853. void fail(char *s)       { puts(s); dos_exit(1); }
  854.  
  855. /* Determines if DPMI is present and, if so, switches into
  856.    protected mode */
  857. BOOL dpmi_init(void)
  858. {
  859.     void (far *dpmi)();
  860.     unsigned hostdata_seg, hostdata_para, dpmi_flags;
  861.     
  862.     _asm {
  863.         mov ax, 1687h           // test for DPMI presence
  864.         int 2Fh
  865.         and ax, ax
  866.         jnz nodpmi              // if (AX == 0) DPMI is present
  867.         mov dpmi_flags, bx
  868.         mov hostdata_para, si   // paras for DPMI host private data
  869.         mov dpmi, di
  870.         mov dpmi+2, es          // DPMI protected-mode switch entry point
  871.         jmp short gotdpmi
  872.         }
  873. nodpmi:
  874.     return FALSE;
  875. gotdpmi:
  876.     if (_dos_allocmem(hostdata_para, &hostdata_seg) != 0)
  877.         fail("can't allocate memory");
  878.     
  879.     /* enter protected mode */
  880.     _asm {
  881.         mov ax, hostdata_seg
  882.         mov es, ax
  883.         mov ax, dpmi_flags
  884.         }
  885.     (*dpmi)();
  886.         
  887.     return TRUE;
  888. }
  889.  
  890. /* Performs a real-mode interrupt from protected mode */
  891. BOOL dpmi_rmode_intr(unsigned intno, unsigned flags, 
  892.     unsigned copywords, RMODE_CALL far *rmode_call)
  893. {
  894.     if (flags) intno |= 0x100;
  895.     _asm {
  896.         push di
  897.         push bx
  898.         push cx
  899.         mov ax, 0300h       // simulate real-mode interrupt
  900.         mov bx, intno       // interrupt number, flags
  901.         mov cx, copywords;  // words to copy from pmode to rmode stack
  902.         les di, rmode_call  // ES:DI = address of rmode call struct
  903.         int 31h             // call DPMI
  904.         jc error
  905.         mov ax, 1           // return TRUE
  906.         jmp short done
  907.         }
  908. error:  
  909.         _asm mov ax, 0           // return FALSE
  910. done:   
  911.         _asm pop cx
  912.         _asm pop bx
  913.         _asm pop di
  914. }
  915.  
  916. /* Allocates a single protected-mode LDT selector */
  917. unsigned dpmi_sel(void)
  918. {
  919.     _asm {
  920.         mov ax, 0           // Allocate LDT Descriptors
  921.         mov cx, 1           // allocate just one
  922.         int 31h             // call DPMI
  923.         jc err
  924.         jmp short done      // AX holds new LDT selector
  925.         }
  926. err:    
  927.         _asm mov ax, 0      // failed
  928. done:;   
  929. }
  930.  
  931. BOOL dpmi_set_descriptor(unsigned pmodesel, DESCRIPTOR far *d)
  932. {
  933.     _asm {
  934.         push di
  935.         push bx
  936.         mov ax, 000ch       // Set Descriptor
  937.         mov bx, pmodesel    // protected mode selector
  938.         les di, d           // descriptor
  939.         int 31h             // call DPMI
  940.         jc error
  941.         mov ax, 1           // return TRUE
  942.         jmp short done
  943.         }
  944. error:  
  945.         _asm mov ax, 0      // return FALSE
  946. done:   
  947.         _asm pop di
  948.         _asm pop bx
  949. }
  950.  
  951. BOOL dpmi_get_descriptor(unsigned pmodesel, DESCRIPTOR far *d)
  952. {
  953.     _asm {
  954.         push di
  955.         mov ax, 000bh       // Get Descriptor
  956.         mov bx, word ptr pmodesel    // protected mode selector
  957.         les di, dword ptr d // descriptor
  958.         int 31h             // call DPMI
  959.         jc error
  960.         mov ax, 1           // return TRUE
  961.         jmp short done
  962.         }
  963. error:  
  964.         _asm xor ax, ax     // return FALSE
  965. done:
  966.         _asm pop di
  967. }
  968.  
  969. BOOL dpmi_sel_free(unsigned pmodesel)
  970. {
  971.     _asm {
  972.         mov ax, 0001h       // Free LDT Descriptor
  973.         mov bx, pmodesel    // selector to free
  974.         int 31h             // call DPMI
  975.         jc error
  976.         mov ax, 1           // return TRUE
  977.         jmp short done
  978.         }
  979. error:  
  980.         _asm mov ax, 0           // return FALSE
  981. done:;
  982. }
  983.  
  984. void far *get_doslist(void)
  985. {
  986.     _asm {
  987.         xor bx, bx
  988.         mov es, bx
  989.         mov ah, 52h
  990.         int 21h
  991.         mov dx, es
  992.         mov ax, bx
  993.         }
  994. }
  995.  
  996. main()
  997. {
  998.     DESCRIPTOR d;
  999.     RMODE_CALL r;
  1000.     void far *fp;
  1001.     char far *doslist = (char far *) 0;
  1002.     unsigned long addr;
  1003.     unsigned pmodesel;
  1004.     unsigned offset, lastdrv_ofs, lastdrv;
  1005.  
  1006.     /* program requires small model! */
  1007.     assert((sizeof(void*) == 2) && (sizeof(void (*)()) == 2));
  1008.     
  1009.     assert(sizeof(ACCESS) == 1);
  1010.     assert(sizeof(DESCRIPTOR) == 8);
  1011.     
  1012.     /* Determine if DPMI present and, if so, switch to protected mode */  
  1013.     if (dpmi_init())
  1014.         puts("now in protected mode");
  1015.     else
  1016.         fail("DPMI not present");   
  1017.     
  1018.     /* Call INT 21h AH=52h (Get DOS List Of Lists) */
  1019.     memset(&r, 0, sizeof(RMODE_CALL));
  1020.     r.eax = 0x5200;
  1021.     if (! dpmi_rmode_intr(0x21, 0, 0, &r))
  1022.         fail("DPMI rmode intr failed");
  1023.     FP_SEG(doslist) = r.es;
  1024.     FP_OFF(doslist) = r.ebx;
  1025.     printf("Real mode DOS List Of Lists = %Fp\r\n", doslist);
  1026.     
  1027.     /* doslist now holds a real-mode address: in order to address it
  1028.        in protected mode, allocate an LDT descriptor and set its 
  1029.        contents; when done, deallocate the LDT descriptor
  1030.     */
  1031.     if (! (pmodesel = dpmi_sel()))
  1032.         fail("DPMI can't alloc pmode selector");
  1033.     
  1034.     /* set size of segment */
  1035.     d.limit = 0xFFFF;
  1036.     
  1037.     /* set base address of segment */
  1038.     addr = ABSADDR(r.es, 0);
  1039.     d.addr_lo = addr & 0xFFFF;
  1040.     d.addr_hi = addr >> 16;
  1041.     d.addr_xhi = 0;             /* IMPORTANT! */
  1042.     
  1043.     /* set access-rights of segment */
  1044.     d.access.accessed = 0;      /* never been used */
  1045.     d.access.read_write = 1;    /* read-write */
  1046.     d.access.conf_exp = 0;      /* not a stack */
  1047.     d.access.code = 0;          /* data */
  1048.     d.access.xsystem = 1;       /* not system descriptor */
  1049.     fp = (void far *) main;
  1050.     d.access.dpl = FP_SEG(fp) & 3;  /* protection level */
  1051.     d.access.present = 1;       /* it's present in memory */
  1052.     d.reserved = 0;
  1053.  
  1054.     if (! dpmi_set_descriptor(pmodesel, &d))
  1055.         fail("DPMI can't set descriptor");
  1056.     
  1057.     FP_SEG(doslist) = pmodesel; /* convert to protected-mode address */
  1058.     FP_OFF(doslist) = r.ebx;
  1059.     printf("Protected mode DOS List Of Lists = %Fp\r\n", doslist);
  1060.     
  1061.     /* now have protected-mode selector to DOS List of Lists */
  1062.     /* Get LASTDRIVE number, print LASTDRIVE letter */
  1063.     lastdrv = doslist[_osmajor==3 && _osminor==0 ? 0x1b : 0x21];
  1064.     printf("LASTDRIVE=%c\r\n", 'A' - 1 + lastdrv);
  1065.     
  1066.     if (! dpmi_sel_free(pmodesel))
  1067.         fail("DPMI can't free selector");
  1068.     
  1069.     /* in protected mode, flush output and quit */
  1070.     fflush(stdout);
  1071.     dos_exit(0);
  1072.  
  1073. dpmifail:
  1074.     fail("DPMI failure");
  1075. }
  1076.  
  1077. ;----------------------------------------------------------------------
  1078.  
  1079.                             THE END?
  1080.  
  1081.  
  1082.